/*
AI Code for Invasion Force - an Explore/Conquer Strategic Wargame
Copyright (C) 1996  Brannen Hough

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/*
	cyber1.c -- artificial intelligence module for Empire II

*/

/* This file contains all the routines associated with AI Type #1.
*/

#include "global.h"


/***************************************************************
*************** Production Routines  ***************************
***************************************************************/

struct GovNode*  AI1_locate_gov( struct City* metro )
{
    struct GovNode *Gov = (struct GovNode *)GovList.mlh_Head;
    struct GovNode *NewGov = NULL;

    for ( ; Gov->gnode.mln_Succ; Gov = (struct GovNode *)Gov->gnode.mln_Succ) {
	if ((Gov->owner == player) && 
	    ((Gov->type == GOV_CITY) || (Gov->type == GOV_PORT))
	    && (Gov->x == metro->col) && (Gov->y == metro->row)) {
	    /* We found the right city governor. */
	    return( Gov );
	}  /* End if */
    } /* End for loop */

    /* Never found it */
    NewGov = AI1_add_gov( metro );
    /* And, let's set up the governors area of interest */
    AI1_setup_area_of_interest (NewGov);
    return (NewGov);
}


struct GovNode*  AI1_add_gov( struct City* metro )
{
    struct GovNode *new_gov = AllocVec((int)sizeof(*new_gov),MEMF_CLEAR);

    new_gov->x = metro->col;
    new_gov->y = metro->row;
    new_gov->targx = -1;
    new_gov->targy = -1;
    new_gov->searchx = -1;
    new_gov->searchy = -1;
    new_gov->mode = GOV_SEARCH;
    new_gov->flags = 0;
    new_gov->owner = player;
    if (port_cityP(metro))  new_gov->type = GOV_PORT;
    else  new_gov->type = GOV_CITY;
    new_gov->ID = NewGov++;

    sprintf (outbuf, "Creating Governor %ld at %ld,%ld",
	     new_gov->ID, new_gov->x, new_gov->y);
    DEBUG_AI(outbuf)
    

    /* Add the new governor to the list of governors */
    AddTail((struct List *)&GovList,(struct Node *)new_gov);
    return( new_gov );
}


void  AI1_set_gov_prod (struct City *metro, struct GovNode *CityOwner)
{    
    CityOwner->req.type = RIFLE;
    CityOwner->req.req_gov = CityOwner->ID;
    CityOwner->req.priority = 9;
    /* And we set the city itself to produce this */
    if (metro->unit_type != CityOwner->req.type) {  /*making a new type */
        metro->unit_type = CityOwner->req.type;
        /* Tooling up penalty */
        metro->unit_wip = -1 * wishbook[metro->unit_type].build/5;
        /*sprintf (outbuf, "Tooling Up the city to build a %s",
        	UnitString[metro->unit_type]);
        DEBUG_AI(outbuf) */
    }
    /* unit->wip has been set to zero elsewhere.  Don't zero out production
       that has already been started when we haven't changed the type.
       We have enough handicaps as it is. */
    
    return;
}


void  AI1_play_turn ( int new_units )
{
    int   MaxLooping = 1000;
    /* Here we may want to look around, give out some orders to units,
       and execute orders to units in a loop until we have done all the 
       moves possible for the units.
       */
    AI1_do_all_histograms();
    AI1_give_orders();
    /* We'll add a little failsafe so we don't spend eternity here */
    while( (MaxLooping > 0) && (!AI1_do_unit_actions()) )  MaxLooping--;
    if( MaxLooping <= 0 ) 
	DEBUG_AI("Exitting AI player's turn - out of actions")
    return;
}


void  AI1_setup_area_of_interest( struct GovNode* Gov )
{
    /* Set up the immediate area of interest */
    Gov->startx = Gov->x - GOVERNOR_RADIUS;
    Gov->starty = Gov->y - GOVERNOR_RADIUS;
    Gov->endx = Gov->x + GOVERNOR_RADIUS;
    Gov->endy = Gov->y + GOVERNOR_RADIUS;
    if (!wrap) {
        if (Gov->startx < 0) Gov->startx = 0;
        if (Gov->starty < 0) Gov->starty = 0;
        if (Gov->endx > width - 1) Gov->endx = width - 1;
        if (Gov->endy > height - 1) Gov->endy = height - 1;
    }
    else {
	if (Gov->startx < 0) Gov->startx += width;
	if (Gov->starty < 0) Gov->starty += height;
	if (Gov->endx > width - 1) Gov->endx -= width;
	if (Gov->endy > height - 1) Gov->endy -= height;
    }
    /* Set up the Extended area of interest */
    Gov->Estartx = Gov->x - EXTENDED_RADIUS;
    Gov->Estarty = Gov->y - EXTENDED_RADIUS;
    Gov->Eendx = Gov->x + EXTENDED_RADIUS;
    Gov->Eendy = Gov->y + EXTENDED_RADIUS;
    if (!wrap) {
        if (Gov->Estartx < 0) Gov->Estartx = 0;
        if (Gov->Estarty < 0) Gov->Estarty = 0;
        if (Gov->Eendx > width - 1) Gov->Eendx = width - 1;
        if (Gov->Eendy > height - 1) Gov->Eendy = height - 1;
    }
    else {
	if (Gov->Estartx < 0) Gov->Estartx += width;
	if (Gov->Estarty < 0) Gov->Estarty += height;
	if (Gov->Eendx > width - 1) Gov->Eendx -= width;
	if (Gov->Eendy > height - 1) Gov->Eendy -= height;
    }
		  
    /* sprintf (outbuf,
       "For Gov %ld, Startx:%ld, Starty:%ld, Endx:%ld, Endy:%ld",
       Gov->ID, Gov->startx, Gov->starty, Gov->endx, Gov->endy);
       DEBUG_AI(outbuf)
       */
    return;
}


void  AI1_do_all_histograms()
{
    struct GovNode *Gov = (struct GovNode *)GovList.mlh_Head;
    /* First we update the picture for all the Governors */
    for ( ; Gov->gnode.mln_Succ; Gov = (struct GovNode *)Gov->gnode.mln_Succ)
	if (Gov->owner == player) {
	    AI1_do_one_histogram (Gov);
        AI1_set_gov_mode (Gov);
	}
    /* End for loop */

}


void  AI1_do_one_histogram( struct GovNode* Gov)
{
    int   j, k;
    struct MapIcon *icon = (struct MapIcon *)PLAYER.icons.mlh_Head;
    struct Unit *unit = (struct Unit *)unit_list.mlh_Head;
    
    /* Zero out the histogram for the governor */
    for (j = 0; j < 16; j++)  {
        Gov->hist.TerrainCounts[j] = 0;
        Gov->hist.UnitCounts[j] = 0;
        Gov->hist.EnemyCounts[j] = 0;
    }
    Gov->hist.TotalEUnits = 0;
    Gov->hist.TotalMyUnits = 0;
    Gov->hist.TotalCities = 0;
    Gov->hist.TotalMyCities = 0;
    
    for (j = Gov->startx; j <= Gov->endx ; j++) {
        for (k = Gov->starty; k <= Gov->endy; k++) {
            Gov->hist.TerrainCounts[get(PLAYER.map,j,k)]++;
        } /* End for k */
    } /* End for j */
            
    SetNeutralCity (Gov);
    for (; icon->inode.mln_Succ; icon = (struct MapIcon *)
      icon->inode.mln_Succ) {
        if ((icon->owner != player) && (icon->col >= Gov->Estartx)
            && (icon->col <= Gov->Eendx) && (icon->row >= Gov->Estarty)
            && (icon->row <= Gov->Eendy)) {
	    Gov->hist.EnemyCounts[icon->type]++;
	    if (icon->type != CITY)  Gov->hist.TotalEUnits++;
	    /*sprintf (outbuf, "City %ld found enemy %s at %ld,%ld", 
	      Gov->ID, UnitString[icon->type],icon->col, icon->row);
	      DEBUG_AI(outbuf)
	      */
	    if ((icon->owner != 0) && (icon->type == CITY)) 
		ClearNeutralCity (Gov);
        }  /* End if enemy unit detected in our area */
    } /* End for loop for icons */

    for (; unit->unode.mln_Succ; unit = (struct Unit *)unit->unode.mln_Succ) {
        if ((unit->owner == player) &&
	    (Gov->ID == atoi(unit->name))) {
	    Gov->hist.UnitCounts[unit->type]++;
	    Gov->hist.TotalMyUnits++;
        }  /* End if we own it and it is owned by this governor */
    } /* End for looking at friendly units */
}


void  AI1_set_gov_mode( struct GovNode* Gov )
{
    if ((Gov->type == GOV_CITY) || (Gov->type == GOV_PORT)) {
	/* Check to see if we still own our city */
	    if (hex_owner(Gov->x, Gov->y) == player) ClearCityTaken(Gov);
	    else {
            SetCityTaken(Gov);
            // clear all orders
            AI1_clear_all_orders(Gov);
        }
    }

    return;
}


void  AI1_clear_all_orders( struct GovNode* Gov )
{
    struct Unit* unit = (struct Unit*) unit_list.mlh_Head;
    // Mode has changed - clear all orders for all units owned by gov
    for (; unit->unode.mln_Succ; unit = (struct Unit *)
        unit->unode.mln_Succ) 
            if ((unit->owner == player) && (atoi(unit->name) 
                == Gov->ID))  clear_orders(unit);
            // End if all that stuff
    // End for loop
    return;
}


void  AI1_give_orders()
{
    struct  Unit   *unit = (struct Unit *)unit_list.mlh_Head;
    struct GovNode *Gov = NULL;
   
    for (;unit->unode.mln_Succ; unit = (struct Unit *)unit->unode.mln_Succ) 
        if ((unit->owner == player) && (unit->move > 0) && 
            (unit->orders == NULL)) {
            /* No orders yet, let's set some
	       Ok, we own the unit, and it has moves left, and has a Governor
	       owner, and has no standing orders
	       Don't just stand there, do something!
	       */
	    /* sprintf(outbuf, "Giving initial orders to unit named %s at %ld,%ld", 
               unit->name, unit->col, unit->row);
               DEBUG_AI(outbuf)
	       */
            Gov = AI1_FindOwner (unit);
            if (Gov != NULL) {
		if( IsCityTaken( Gov ) ) {
		    /* Have the unit move to the city's location */
		    AI1_computer_give_orders (unit, C_ORDER_GOTO, Gov->x, 
					      Gov->y, -1, -1, -1);
		}
		else {
		    /* Have the unit wander randomly */
		    AI1_computer_give_orders (unit, C_ORDER_RANDOM, -1, -1, 
					      -1,-1, -1);
		}
	    }
	} /* end if owner and has moves left */
    /* End For */
}

struct GovNode* AI1_FindOwner (struct Unit *unit)
{
    int owner;
    struct GovNode *Gov = (struct GovNode *)GovList.mlh_Head;
    
    if (unit->name == NULL)  {
        DEBUG_AI("Unit name is NULL")
	return (NULL);
    }

    owner = atoi(unit->name);

    for ( ; Gov->gnode.mln_Succ; Gov = (struct GovNode *)Gov->gnode.mln_Succ)
        /* We have two checks and three fail safes here.
	   We must make sure we don't cheat and that the names are
	   not NULL (strcmp is not defined in that case)
	   */
	if ((Gov->owner == unit->owner) && (Gov->owner == player) 
	    && (Gov->ID == owner))
	    return (Gov);
    /* End For, If */
    sprintf (outbuf,"Unable to find owning Governor for unit %s", unit->name);
    DEBUG_AI(outbuf)

    return (NULL);
}


void AI1_computer_give_orders(struct Unit *unit,int suborder,short destx,
    short desty,short orgx,short orgy,int etc)
{
   struct Order *order=AllocVec((long)sizeof(*order),MEMF_CLEAR);

   clear_orders(unit);
   if (order) {
      order->destx = destx;
      order->desty = desty;
      unit->orders = order;
      order->type = ORDER_NONE;
      if (orgx == -1)  order->orgx = unit->col;
      else  order->orgx = orgx;
      if (orgy == -1)  order->orgy = unit->row;
      else  order->orgy = orgy;
      order->processed = FALSE;
      order->etc = etc;
      order->reserved = suborder;       /* using reserved for computer
                                  orders so I don't tread on the human
                                  player's tokens. */
                                        
      /* This is set up to be in the order they are used most often,
         and set up so that more order types can be added as needed.
	 */
      switch (suborder) {
      case C_ORDER_GOTO:
	  /* sprintf (outbuf, "%s %s C_ORDER_GOTO from %ld,%ld to %ld,%ld",
	     UnitString[unit->type], unit->name, order->orgx, 
	     order->orgy, order->destx, order->desty);
	     DEBUG_AI(outbuf)
	     */
	  break;
      case C_ORDER_RANDOM:
	  /* Standard stuff only */
	  break;
      }
   }
}

int  AI1_do_unit_actions()
{
    struct Unit *unit = (struct Unit *)unit_list.mlh_Head;
    struct GovNode *Gov = NULL;
    int         Done = TRUE;
   

    for ( ; unit->unode.mln_Succ; unit = (struct Unit *) unit->unode.mln_Succ)
        if ((unit->owner==player) && (unit->move > 0)) {
            /* Skip Sentry units */
            if ((unit->orders == NULL) || 
		(unit->orders->reserved != C_ORDER_SENTRY))   Done = FALSE;
	    
	    /* Take a look around us and react to enemy units + cities */
	    if( AI1_look_around ( unit ) )   return (FALSE);

            /* if we have orders, execute them */
            if (unit->orders != NULL) {
                AI1_execute_standing_order(unit);
                return (Done);
            }

            else {
                Gov = AI1_FindOwner (unit);
                if (Gov != NULL) {
		    DEBUG_AI("No orders, no enemy around. Huh?")
		    unit->move = 0;
		    return (Done);
                } /* End if Gov != NULL */
                else {
		    DEBUG_AI("Cannot find unit owner to ad lib turn")
                    DEBUG_AI("For Now, Forget IT!")
                    sprintf (outbuf, "%s Unit %s at %ld,%ld",
			     UnitString[unit->type], unit->name, unit->col,
			     unit->row);
		    DEBUG_AI(outbuf)
                    unit->move = 0;
		    return (Done);
                } /* End else Gov == NULL */
	    } /* End else figure something out */
        } /* End if we own unit and it has moves left */
    /* End for loop */
    
    return (Done);
}


int  AI1_look_around( struct Unit* unit)
{
    int          i, k;
    short        targx, targy;
    struct City* metro;

    for (i = 0; i < 6; i++) {
	if (AI1_calc_dir (i, unit->col, unit->row, &targx, &targy) != -1) {
	    if ((metro = city_hereP (targx, targy)) && 
		(metro->owner != player)) {
		/* if there is a city here and we don't own it */
		if ((unit->type == RIFLE) || (unit->type == ARMOR)
		    || (unit->type == AIRCAV)) {
		    /* Go for it! */
		    /* DEBUG_AI("Jumping enemy city") */
		    (void) move_unit_xy (unit, targx, targy);
		    return (1);
		} /* End if can take city */
	    } /* End if city here and it ain't ours! */
	} /* End if hex here */
    } /* End For loop */
    for (i = 0; i < 6; i++) {
	if (AI1_calc_dir (i, unit->col, unit->row, &targx, &targy) != -1) {
	    if (((k = hex_owner(targx,targy)) > 0)&&(k!=player)) {
		/* Somebody there - for now just jump 'em */
		/*DEBUG_AI("Jumping enemy unit") */
		(void) move_unit_xy (unit, targx, targy);
		return (1);
	    }  /* End if bad guy here */
	} /* End if hex here */
    } /* End for loop */
    return (0);
}


/* This routine calculates the new coordinates resulting from heading in
   a particular direction from given coordinates.  Returns -1 for not
   possible, and a 1 for OK.
   */
int AI1_calc_dir (enum Direction dir, short orgx, short orgy, 
short *x, short *y)
{
   int  targx = orgx;
   int  targy = orgy;
   
   switch (dir) {
      case EAST:
         targx++;
         break;
      case WEST:
         targx--;
         break;
      case NORTHEAST:
         targy--;
         if (orgy%2) /* odd number */
            targx++;
         break;
      case NORTHWEST:
         targy--;
         if (!(orgy%2)) /* even number */
            targx--;
         break;
      case SOUTHEAST:
         targy++;
         if (orgy%2) /* odd number */
            targx++;
         break;
      case SOUTHWEST:
         targy++;
         if (!(orgy%2)) /* even number */
            targx--;
         break;
      default:
            return (-1);
    }

   /* correct values for wrap, if it's active */
   if (wrap) {
      if (targx<0)
         targx += width;
      if (targx>=width)
         targx -= width;
      if (targy<0)
         targy += height;
      if (targy>=height)
         targy -= height;
   }

   /* he certainly can't move off the map! */
   if (targx<0 || targx>=width || targy<0 || targy>=height) {
        DEBUG_AI("Moving Off Map")
   		return(-1);
   }

   *x = targx;
   *y = targy;
   
   return (1);
}

void  AI1_execute_standing_order( struct Unit* unit)
{
    int		result;

    if ((unit->orders == NULL) || (unit->orders->type != ORDER_NONE)) {
	DEBUG_AI("Big problem in execute_standing_order - no orders!")
	sprintf (outbuf, "%s %s has no standing orders to execute - aborting",
		 UnitString[unit->type], unit->name);
	DEBUG_AI(outbuf)
	return;
    }
    
    switch (unit->orders->reserved) {
        case  C_ORDER_GOTO:
            result = AI1_command_headto(unit);
            if (result < 0) clear_orders(unit);   /* problem */
            if (result == 0) clear_orders(unit);  /* done */
            break;
        case  C_ORDER_RANDOM:
            AI1_command_random(unit);
            break;
        default:
            DEBUG_AI("Unknown command type found in execute_standing_order!")
            break;
    }
    return;
}


void  AI1_command_random( struct Unit* unit )
{
    enum	Direction Randdir = RangeRand(6L);
    int     i;

    /* About as simple as it gets! We'll try 10 times to make a valid move.*/
    for( i=0; i < 10; i++ ) {
	if(  move_unit_dir(unit, Randdir) != -1 )  return;
	Randdir = RangeRand(6L);
    }
    return;
}


int   AI1_command_headto( struct Unit* unit )
{
    int  i, j;
    int  result;

    /* Check to see if we have arrived */
    if ((unit->col == unit->orders->destx) && 
        (unit->row == unit->orders->desty))  {
        /* We have arrived! */
        /* DEBUG_AI("We have arrived!") */
        return (0);
    }

    /* Select the correct direction to look in */
    i = 0;
    if (unit->orders->destx - unit->col < 0)  i = 3;
    if (unit->orders->destx == unit->col)     i = 6;
    if (unit->orders->desty - unit->row < 0)  i += 1;
    if (unit->orders->desty == unit->row)     i += 2;
    
    /* Let's move in the three most likely directions only */
    for (j = 0; j < 3; j++) {
        /* -1 is the only return value where a move might still be possible
	   We must be sure we only do ONE move - ever */
	if ((result = move_unit_dir(unit, DirArray[i][j])) != -1) {
            /*sprintf (outbuf, "Moved %s, result %ld", 
	      DirString[DirArray[i][j]], result);
	      DEBUG_AI(outbuf)
	      */
            if (result > -3) {
                /* We didn't die in an attack, and we didn't crash, so 
		   let's check for destination again
		   */
                /*DEBUG_AI("Checking for Destination") */
                if ((unit->col == unit->orders->destx) && 
                    (unit->row == unit->orders->desty))  {
                    /* We have arrived! */
                    /* DEBUG_AI("We have arrived!") */
                    return (0);
                }
                unit->orders->etc--;
                if (unit->orders->etc < 0)  {
                    DEBUG_AI("Watchdog on HeadTo ran out")
		    return (-2);
                }
            }
            return (1);
        }
        else {
            sprintf (outbuf, "%s %s Tried to move in direction %s, result %ld",
                UnitString[unit->type], unit->name, DirString[DirArray[i][j]], 
                result);
            DEBUG_AI(outbuf)
        }
    } /* End for loop */
    
    /* No luck at all */
    DEBUG_AI ("Cannot move towards - clearing orders")
    return (-1);
}